package au.id.weston.scott.Vincent;

// Copyright (c) 2009, Scott Weston <scott@weston.id.au>
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//     * Neither the name of Scott Weston nor the names of contributors may be
//       used to endorse or promote products derived from this software without
//       specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFileInputStream;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.media.MediaScannerConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.RemoteViews;

public class sambaservice extends Service {
	
	public final static String ACTION_COPY = "au.id.weston.scott.Vincent.SAMBA_COPY";
	public final static String ACTION_STOP = "au.id.weston.scott.Vincent.SAMBA_STOP";
	
	private final static String TAG = "sambaservice";
	static MediaScannerConnection mScanner;
	static Context mContext;

	final static int COPIER_COPY = 1;
	final static int COPIER_DIE  = 2;
	final static int COPIER_STOP = 3;
	
	private static Handler copierHandler;
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}
	
	@Override
	public void onCreate() {
		Log.d(TAG, "onCreate()");
		
		mContext = this;
		
        jcifs.Config.setProperty("jcifs.encoding", "US-ASCII");
        jcifs.Config.setProperty("jcifs.smb.lmCompatibility", "0");
        jcifs.Config.setProperty("jcifs.netbios.hostname", "sambasync");
        
        copier.start();
        
        mScanner = new MediaScannerConnection(mContext, 
        		new MediaScannerConnection.MediaScannerConnectionClient() {
        			public void onMediaScannerConnected() {
        			}
        			public void onScanCompleted(String path, Uri uri) {
        			}
        		});
        
        IntentFilter iFilt = new IntentFilter();
        iFilt.addAction(ACTION_COPY);
        iFilt.addAction(ACTION_STOP);
        registerReceiver(mIntentReceiver, iFilt);
        
        super.onCreate();
	}
	
	BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
		public void onReceive(Context context, Intent intent) {
			Log.d(TAG, "received: " + intent.toString());
			
			if (intent.getAction().equals(ACTION_COPY)) {
				Log.d(TAG, "ACTION_COPY received");

				Bundle extras = intent.getExtras();
				Log.d(TAG, "keys: " + extras.keySet());

				Message msg = Message.obtain();
				msg.obj = extras.getString("mount_name");
				msg.what = COPIER_COPY;
				msg.setData(extras);
				copierHandler.removeMessages(COPIER_COPY, extras.getString("mount_name"));	// remove any outstanding copies for this mount
				if (extras.containsKey("delay")) {
					copierHandler.sendMessageDelayed(msg, extras.getLong("delay"));
				} else {
					copierHandler.sendMessage(msg);
				}
			} else if (intent.getAction().equals(ACTION_STOP)) {
				Log.d(TAG, "ACTION_STOP received");
				copierHandler.removeMessages(COPIER_COPY);
				copierHandler.sendEmptyMessage(COPIER_STOP);
			}
		}
	};
	
	protected static final Thread copier = new Thread(new Runnable() {

		public void run() {
			Log.d(TAG, "copierThread.running(): id=" + copier.getId());
			Looper.prepare();

			copierHandler = new Handler() {
				public void handleMessage(Message msg) {
					Log.d(TAG, "copierThread.received(): " + msg);
					switch (msg.what) {
					case COPIER_COPY:
						Log.d(TAG, "copierThread.copy():");

				        WifiManager wm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
				        WifiLock wl = wm.createWifiLock("Vincent/sambasync/copy");
			        	wl.acquire();
						performCopy(msg.getData());
						wl.release();
						
						return;
					case COPIER_DIE:
						this.getLooper().quit();
						return;
					}
				}
			};

			Looper.loop();
			Log.w(TAG, "looper().quit()");
		}
		
		// iff the network is available do a copy.
		
		private void performCopy(Bundle data) {
			
			ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
			if (cm == null) {
				Log.d(TAG, "unable to get CONNECTIVITY_SERVICE connection, skipping copy attempt");
				return;
			}
			if (cm.getActiveNetworkInfo().getType() != ConnectivityManager.TYPE_WIFI ||
					!cm.getActiveNetworkInfo().isConnected()) {
				Log.d(TAG, "No valid network connectivity, ignoring copy request.");
				return;
			}
			
			String username = data.getString("username");
			String password = data.getString("password");
			String server = data.getString("server");
			String share = data.getString("share");
			String directory = data.getString("directory");
			String dest = data.getString("dest");
			
			SmbFile file;
	        SmbFile[] files = new SmbFile[0];
        	
        	MediaPlayer mp_start = MediaPlayer.create(mContext, R.raw.start);
        	MediaPlayer mp_copied = MediaPlayer.create(mContext, R.raw.copied);
        	MediaPlayer mp_error = MediaPlayer.create(mContext, R.raw.error);
        	MediaPlayer mp_finish = MediaPlayer.create(mContext, R.raw.finish);
        	
	        try {
	        	mp_start.stop();
	        	mp_start.prepare();
	        	mp_start.start();
	        	
	        	showNotification("Logging in...", 0, 0, 0, 0);
	        	
	        	NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(null, 
	        			username, password);
	        	String smburl = String.format("smb://%s/%s/%s/", server, share, directory);
	        	file = new SmbFile(smburl, auth);
	        	
	        	showNotification("Getting file list...", 0, 0, 0, 0);
	        	files = file.listFiles();
	    
	        	try {
	        		File dir = new File(dest);
	        		dir.mkdirs();
	        	} catch (Exception e) {
	        		showNotification("Failed to use destination, SD card inserted and writable?", 1, 1, 1, 1);
	        		Log.e(TAG, "unable to mkdirs the dest.");
	        		return;
	        	}
								
	        	int errors = 0;
	        	int total = 0;
	        	int skip = 0;
	        	
				for( int i = 0; i < files.length; i++ ) {
					
					if (copierHandler.hasMessages(COPIER_STOP)) {
						showNotification("Copy stopped at user request", 1, 1, 1, 1);
						break;
					}
					
					Log.d(TAG, "found: filename: [" + files[i].getName() + "]");
					
					File localfile = new File(dest + "/" + files[i].getName());

					if (!localfile.exists()) {
						try {
							showNotification("Copying " + files[i].getName() + " to " + dest + "/" + files[i].getName(), 1, 0, files.length, i);
							Log.d(TAG, "Copying " + files[i].getName() + " to " + dest + "/" + files[i].getName());
							SmbFileInputStream in = new SmbFileInputStream( files[i] );
							
							FileOutputStream out = new FileOutputStream( 
									dest + "/" + files[i].getName() );

							byte[] b = new byte[8192];
							int n, tot = 0;
							while(( n = in.read( b )) > 0 ) {
								out.write( b, 0, n );
								tot += n;
								if (copierHandler.hasMessages(COPIER_STOP)) {
									showNotification("Copy of " + files[i].getName() + " aborted", 1, 0, 1, 0);
									in.close();
									out.close();
									localfile.delete();
									if (mScanner.isConnected()) {
										mScanner.disconnect();
									}
									
									mp_error.stop();
									mp_error.prepare();
						        	mp_error.start();
						        	
						        	while (mp_error.isPlaying()) {
						        		Thread.sleep(500L);
						        	}
						        	
						        	mp_start.release();
							        mp_copied.release();
							        mp_error.release();
							        mp_finish.release();
							        
									return;
								} else {
									showNotification("Copying " + files[i].getName() + " to " + dest + "/" + files[i].getName(), (int)files[i].length(), tot, files.length, i);
								}
							}

							in.close();
							out.close();
							total++;
							if (!mScanner.isConnected()) {
								mScanner.connect();
								Thread.sleep(1000);
							}
							mScanner.scanFile(dest + "/" + files[i].getName(), null);
							mp_copied.stop();
							mp_copied.prepare();
							mp_copied.start();
						} catch (Exception e) {
							Log.e(TAG, "copy failed: " + e);
							e.printStackTrace();
							showNotification("Copy failed: " + e.getMessage(), 1, 1, 1, 1);
							errors++;
							localfile.delete();
							mp_error.stop();
							mp_error.prepare();
							mp_error.start();
							continue;
						}
						Log.d(TAG, "copied: " + files[i].getName());
	        		} else {
	        			Log.d(TAG, "cowardly skipping file: " + files[i].getName());
	        			showNotification("Copying " + files[i].getName() + " to " + dest + "/" + files[i].getName(), 1, 1, files.length, i);
	        			skip++;
	        		}
	        	}
				showNotification(String.format("Copy complete.  %d/%d/%d copy/skip/error.", total, skip, errors), 1, 1, 1, 1);
	        } catch (Exception e) {
	        	Log.e(TAG, "error: " + e);
	        	e.printStackTrace();
	        	showNotification("Copy failed: " + e.getMessage(), 1, 1, 1, 1);
	        }
	        
	        Log.d(TAG, "main copy loop complete");
	        
	        Log.d(TAG, "disconnecting from mediaScanner");
	        if (mScanner.isConnected()) {
				mScanner.disconnect();
			}
	        
	        Log.d(TAG, "playing finish sound");
	        try {
	        	mp_finish.stop();
				mp_finish.prepare();
				mp_finish.start();
		        while (mp_finish.isPlaying()) {
		        	Thread.sleep(500L);
		        }
			} catch (IllegalStateException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	        
			Log.d(TAG, "releasing mediaplayers");
	        mp_start.release();
	        mp_copied.release();
	        mp_error.release();
	        mp_finish.release();
		}
	});
	
	private static RemoteViews nmView;

	protected static void showNotification(String text, int max, int tot, int files, int file) {
        NotificationManager nm = (NotificationManager) mContext.getSystemService(Service.NOTIFICATION_SERVICE);

        PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,
                new Intent(mContext, sambasync.class), 0);

        // construct the Notification object.
        Notification notif = new Notification();
        
        notif.tickerText = text;
        notif.icon = R.drawable.icon32;
        
        nmView = new RemoteViews(mContext.getPackageName(), R.layout.copy);
        nmView.setTextViewText(R.id.TextView01, text);
        
        nmView.setProgressBar(R.id.ProgressBar01, max, tot, false);
        nmView.setProgressBar(R.id.ProgressBar02, files, file, false);
        
        notif.contentView = nmView;
        
        notif.contentIntent = contentIntent;
        nm.notify(R.layout.copy, notif);
    }
}
